#include "../../include/common.h"
#include "../../include/core/coroutine.h"
#include "../../include/core/sched.h"

coroutine_t * coroutines[TASK_SIZE] = {0};

extern coroutine_t* current_coroutine;

static int find_free_zid() {
    for (int i = 0; i < TASK_SIZE; ++i) {
        if (NULL == coroutines[i]) {
            return i;
        }
    }

    return -1;
}

uint coroutine_create(OUT uint* pzid, coroutine_fun_t entry, char* name) {
    int zid;
    coroutine_t* coroutine;

    // 找到可用的协程ID
    zid = find_free_zid();
    if (-1 == zid) {
        ERROR_PRINT("协程数量达到最大值\n");
        return -1;
    }

    // 创建微内核实例
    coroutine = malloc(sizeof(coroutine_t));
    if (NULL == coroutine) {
        ERROR_PRINT("malloc fail\n");
        return -1;
    }

    coroutine->state = COROUTINE_INIT;
    coroutine->zid = zid;
    coroutine->fun = entry;

    // 协程的栈要用mmap申请，用malloc申请权限不够
    coroutine->esp3 = mmap(
            NULL,   // 映射区的开始地址，设置为NULL或0表示由系统决定
            getpagesize(),  // 申请的内存大小按照内存页对齐，这里直接调用函数获取内存页大小
            PROT_READ | PROT_WRITE | PROT_EXEC, // 映射的内存区的权限：可读可写可执行
            MAP_ANONYMOUS | MAP_PRIVATE,    // 映射对象的类型：匿名映射，私有映射
            -1,     // 文件描述符，不需要时设置为 -1
            0       // 偏移量，匿名映射时设置为 0
    ) + getpagesize();  // 结果加上内存页的大小。这是因为栈在内存中通常是向下增长的，映射返回的地址是栈顶地址，为了获取栈底地址，需要在栈顶地址上加上栈的大小。
    /**
     * 假设 mmap 返回的起始地址是 0x1000（这是一个示例地址，实际地址由系统分配）。
        这块内存区域的范围是 0x1000 到 0x1000 + 4096。
        栈在内存中通常是向下增长的，因此需要从 0x1000 + 4096 开始。
        coroutine->esp3 + getpagesize() 等于 0x1000 + 4096，即 0x2000
     */

    memcpy(coroutine->name, name, strlen(name));

    // 将准备好的微内核写入调度数组
    coroutines[zid] = coroutine;

    if (NULL != pzid) {
        *pzid = zid;
    }

    // 更新协程状态为ready，表示可调度
    coroutine->state = COROUTINE_READY;

    return 0;
}

void coroutine_exit() {
    if (NULL == current_coroutine) {
        ERROR_PRINT("current_minithread == NULL\n");
        return;
    }

    for (int i = 0; i < TASK_SIZE; ++i) {
        coroutine_t* tmp = coroutines[i];

        if(NULL == tmp) continue;
        if (COROUTINE_RUNNING != tmp->state) continue;

        coroutines[i] = NULL;

        INFO_PRINT("current_minithread exit: %s\n", tmp->name);

        free(tmp);
    }

    sched();
}